package com.lx.spring.web;

import com.lx.spring.AnnoConfig;
import com.lx.spring.AutoWriteAnnoConfig;
import com.lx.spring.Demo;
import com.lx.spring.biz.UserBiz;
import lombok.Data;
import org.springframework.context.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;

import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class MyAnnotationConfigApplicationContext {

    Class configClass;

    Object configObject;

    Map<String, BeanDefinition> BeanDefinitionMap = new HashMap<>();

    static Map<String, Object> singtonObjects = new HashMap<>();


    public MyAnnotationConfigApplicationContext(Class configClass) throws Exception {
        this.configClass = configClass;
        configObject = configClass.newInstance();

        // 读取配置(基于注解) 创建BeanDefinition 对象, 存入创建BeanDefinitionMap 容器

        // 通过方法加入BeanDefinitionMap
        addBeanDefinitionFromMethod();
        //通过扫描注解加入BeanDefinitionMap
        addBeanDefinitionFromPackage();
        BeanDefinitionMap.forEach((id, bean) -> {
            System.out.printf("%s: %s \n", id, bean);
        });

        //根据 BeanDefinition 创建 bean, 存入 IOC singtonObjects
        createBean();
        // 后续实现 getBean

        // 实现自动装载 autowired

    }


    public Object getBean(String id) {
        Object bean = singtonObjects.get(id);
        if (bean == null) {
            throw new RuntimeException("Bean not found: " + id);
        }
        return bean;
    }

    public <T> T getBean(Class<T> tClass) {
        // stream 编程
        List<Map.Entry> list = singtonObjects.entrySet()
                .stream()
                //Class.isAssignableFrom(Class)也就是判断当前的Class对象所表示的类，是不是参数中传递的Class对象所表示的类的父类，超接口，或者是相同的类型。是则返回true，否则返回false。
                .filter(entry -> entry.getValue().getClass().isAssignableFrom(tClass))
                .collect(Collectors.toList());

        if(list.isEmpty()){
            throw new RuntimeException("Bean not found: " + tClass);
        }else if(list.size() == 1){
            return (T)list.get(0).getValue();
        }else{
            List<Map.Entry> list1 = list.stream().filter(entry -> {
                BeanDefinition bd = BeanDefinitionMap.get(entry.getKey());
                return bd.isPrimary();
            }).collect(Collectors.toList());
            if(list.isEmpty()){
                throw new RuntimeException("该Bean没用定义主Bean" + tClass);
            }else if (list1.size() == 1){
                return (T) list1.get(0).getValue();
            }else{
                throw new RuntimeException("定义了多个主Bean" + tClass);
            }
        }
    }

    private void addBeanDefinitionFromPackage() throws Exception {
        ComponentScan componentScan = (ComponentScan)configClass.getAnnotation(ComponentScan.class);
        if (componentScan != null) {
            String[] pakages = componentScan.value();
            URL url = configClass.getClassLoader().getResource("");
            // 编译目录路径
            String classesPath = URLDecoder.decode(url.getPath()).substring(1);
            System.out.println(classesPath);
            //扫描路径
            String scanRootPath;
            List<File> classFileList = new ArrayList<>();
            File classesFile = new File(classesPath);
            for (String aPakage : pakages) {
                System.out.println("aPakage = " + aPakage);
                scanRootPath = classesPath + aPakage.replaceAll("\\.", "/");
                scanClassFile(new File(scanRootPath), classFileList);
            }
            for (File classFile : classFileList) {
                String classPath = classFile.getAbsolutePath().replace(classesFile.getAbsolutePath(), "");
                classPath = classPath.replaceAll(".class", "");
                classPath = classPath.substring(1);
                classPath = classPath.replaceAll("\\\\", ".");
                System.out.println("classPath = " + classPath);
                Class beanClass = Class.forName(classPath);

                Service service = (Service) beanClass.getAnnotation(Service.class);
                Repository repository = (Repository) beanClass.getAnnotation(Repository.class);
                Controller controller = (Controller) beanClass.getAnnotation(Controller.class);
                Component component = (Component) beanClass.getAnnotation(Component.class);

                if( service == null &&
                        repository == null &&
                        controller == null &&
                        component == null ){
                    continue;
                }

                //构建 BeanDefinition
                BeanDefinition bd = new BeanDefinition();
                bd.beanClass = beanClass;
                bd.lazy = beanClass.getAnnotation(Lazy.class) != null;
                bd.primary = beanClass.getAnnotation(Primary.class) != null;
                if(beanClass.getAnnotation(Scope.class) != null) {
                    bd.scope = ((Scope)beanClass.getAnnotation(Scope.class)).value();
                }
                if(service != null && service.value().isBlank() == false) {
                    bd.id = service.value();
                }else if(repository != null && repository.value().isBlank() == false) {
                    bd.id = repository.value();
                }else if(controller != null && controller.value().isBlank() == false) {
                    bd.id = controller.value();
                }else if(component != null && component.value().isBlank() == false) {
                    bd.id = component.value();
                }else{
                    String id = beanClass.getSimpleName();
                    //类名首字母小写
                    bd.id = id.substring(0,1).toLowerCase() + id.substring(1);
                }

                BeanDefinitionMap.put(bd.id, bd);
            }
        }
    }


    private void createBean() throws Exception {
        for (Map.Entry<String, BeanDefinition> entry : BeanDefinitionMap.entrySet()) {
            String id = entry.getKey();
            BeanDefinition bd = entry.getValue();
            if(bd.beanMethod != null){
                // 仅执行无参数的Bean方法
                if(bd.beanMethod.getParameterCount() == 0){
                    bd.beanMethod.setAccessible(true);
                    Object bean = bd.beanMethod.invoke(configObject);
                    singtonObjects.put(id, bean);
                }
            }else{
                Object bean = bd.beanClass.newInstance();
                singtonObjects.put(id, bean);

            }
        }
    }
    //递归扫描文件
    private void scanClassFile(File dir, List<File> fileList) {

        File[] classFiles =  dir.listFiles(file -> file.getName().endsWith(".class"));
        for (File classFile : classFiles) {
            fileList.add(classFile);
        }
        File[] subDirs = dir.listFiles(file -> file.isDirectory());
        for(File subDir : subDirs) {
            scanClassFile(subDir, fileList);
        }
    }
    private void addBeanDefinitionFromMethod() {
        Method[] declaredMethods = configClass.getDeclaredMethods();
        for (Method method : declaredMethods) {
            //判断是否是Bean方法
            if (method.getAnnotation(Bean.class) != null) {
                //Bean方法
                BeanDefinition bd = new BeanDefinition();
                bd.beanMethod = method;
                bd.beanClass = method.getReturnType();
                bd.lazy = method.getAnnotation(Lazy.class) != null;
                bd.primary = method.getAnnotation(Primary.class) != null;
                Bean annotation = method.getAnnotation(Bean.class);
                if (annotation.value().length > 0) {
                    // 有自己命名
                    bd.id = annotation.value()[0];
                }else {
                    //没有自己命名
                    bd.id = method.getName();
                }
                if(method.getAnnotation(Scope.class) != null) {
                    bd.scope = method.getAnnotation(Scope.class).value();
                }
                BeanDefinitionMap.put(bd.id, bd);

            }
        }
    }

    public static void main(String[] args) throws Exception {
        MyAnnotationConfigApplicationContext cxt =
                new MyAnnotationConfigApplicationContext(AutoWriteAnnoConfig.class);
        System.out.println("=====================================================");
        Object userMapper = cxt.getBean("userMapper");
        System.out.println("userMapper = " + userMapper);
        System.out.println("=====================================================");
        UserBiz userBiz = cxt.getBean(UserBiz.class);
        System.out.println("userBiz = " + userBiz);

    }

}

@Data
class BeanDefinition{
    String id;
    Class beanClass;
    Method beanMethod;
    boolean lazy;
    String scope;
    boolean primary;

}
